import { createHash } from "node:crypto";
import fs from "node:fs";

/**
 * 加密字符串 (信息摘要算法)
 *
 * @param {string} algorithm 加密算法：md5, sha1, sha256, sha512
 * @param {string} salt 随机字符串
 * @param {string} raw 明文
 * @returns {string} 密文
 */
export function getHexDigest(algorithm, salt, raw) {
  const hash = createHash(algorithm);
  hash.update(salt + raw);
  return hash.digest("hex");
}

const algo = "md5",
  UNUSABLE_PASSWORD = "!";

export function makePassword(rawPassword) {
  if (!rawPassword) return "!";
  const rand = () => Math.random().toString(16).slice(-8),
    salt = getHexDigest(algo, rand(), rand()).slice(-8),
    hash = getHexDigest(algo, salt, rawPassword);
  return `${salt}:${hash}`;
}

/**
 * 验证密码
 *
 * @param {string} rawPassword 明文
 * @param {string} excPassword 密文
 * @returns {boolean}
 */
export function checkPassword(rawPassword, excPassword) {
  if (excPassword === UNUSABLE_PASSWORD) return false;
  const [salt, hash] = excPassword.split(":");
  return getHexDigest(algo, salt, rawPassword) === hash;
}

/**
 * 判断文件或路径是否存在
 * @param {string} path 文件名或路径名
 * @returns {Promise<boolean>}
 */
export function exists(path) {
  return new Promise((resolve, reject) => {
    // fs.constants.F_OK 代表文件是否存在
    fs.access(path, fs.constants.F_OK, (err) => {
      if (err) reject(err);
      else resolve(true);
    });
  });
}

/**
 * 创建目录
 *
 * @param {string} path 要创建的目录路径
 * @returns {Promise<void>}
 */
export function mkdir(path) {
  return new Promise((resolve, reject) => {
    fs.mkdir(path, { recursive: true }, (err) => {
      if (err) reject(err);
      else resolve();
    });
  });
}

/**
 * 确保目录一定存在
 *
 * @param {string} path 目录名
 */
export async function shouldExistsDir(path) {
  try {
    await exists(path);
  } catch {
    await mkdir(path);
  }
}

export function writeFile(path, data) {
  return new Promise((resolve, reject) => {
    fs.writeFile(path, data, (err) => {
      if (err) reject(err);
      else resolve();
    });
  });
}

export function deleteFile(path) {
  return new Promise((resolve, reject) => {
    fs.unlink(path, (err) => {
      if (err) reject(err);
      else resolve();
    });
  });
}

export function isString(value) {
  return typeof value === "string" || value instanceof String;
}

export function isNumber(value) {
  return typeof value === "number" || value instanceof Number;
}

export function orderBy(fields) {
  if (!fields) return "";
  function descOrAsc(field) {
    return field.startsWith("-") ? `${field.slice(1)} DESC` : field;
  }
  if (Array.isArray(fields)) {
    return fields.map(descOrAsc).join(", ");
  }
  return descOrAsc(fields);
}

export function isEmpty(val) {
  if (Array.isArray(val) || typeof val === "string") {
    return val.length === 0;
  }
  if (val instanceof Object) {
    return Object.keys(val).length === 0;
  }
  return false;
}

/**
 * Splits a given value into an array of numeric and non-numeric units.
 *
 * @param {any} val - The value to be split.
 * @return {Array} An array containing the numeric and non-numeric units.
 */
export function splitNumUnits(val) {
  return val
    .toString()
    .split(/([0-9.]+)/)
    .filter((i) => i);
}
